package com.hoho.android.usbserial.util;

import android.hardware.usb.UsbRequest;
import android.util.Log;

import com.hoho.android.usbserial.driver.CdcTusb3410SerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialDriver;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * Utility class which services a {@link UsbSerialDriver} in its {@link #run()}
 * method. Reading data asynchropnously until the EOL Symbol is detected.
 *
 * @author Christoph Rathfelder (christoph.rathfelder@hsg-imit.de)
 */
public class AsyncReadlnManager implements Runnable {

    private static final String TAG = AsyncReadlnManager.class.getSimpleName();
  
    private static final boolean DEBUG = true;

    private static final int READ_WAIT_MILLIS = 500;
    private static final int BUFSIZ = 4096;
    
    private final UsbSerialDriver mDriver;
    
   
    private final ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ);
    private final ByteBuffer mDataBuffer = ByteBuffer.allocate(BUFSIZ);
    

    
    private byte mEolSymbol = HexDump.hexStringToByteArray("0D")[0];
    
    /**
     * Returns the EOL Byte
     * 
     * @return the byte representing the EOL symbol
     */
    public byte getEolSymbol() {
        return mEolSymbol;
    }
    

    /**
     * Sets the EOL byte using a HexString as input
     * 
     * @param hex the Hex-String representing the Byte
     */
    public void setEolSymbol(String hex) {
        this.mEolSymbol = HexDump.hexStringToByteArray(hex)[0];
    }
    
    /**
     * Sets the EOL byte using a byte as input
     * 
     * @param by the byte representing EOL
     */
    public void setEolSymbol(byte by) {
        this.mEolSymbol = by;
    }

   


    private enum State {
        STOPPED,
        RUNNING,
        STOPPING
    }

    // Synchronized by 'this'
    private State mState = State.STOPPED;

    // Synchronized by 'this'
    private Listener mListener;



    public interface Listener {
        /**
         * Called when new incoming data is available.
         */
        public void onNewData(byte[] data);

        /**
         * Called when {@link SerialInputOutputManager#run()} aborts due to an
         * error.
         */
        public void onRunError(Exception e);
    }

    /**
     * Creates a new instance with no listener.
     */
    public AsyncReadlnManager(UsbSerialDriver driver) {
        this(driver, null);
    }

    /**
     * Creates a new instance with the provided listener.
     */
    public AsyncReadlnManager(UsbSerialDriver driver, Listener listener) {
        mDriver = driver;
        mListener = listener;
    }
    
    public AsyncReadlnManager(UsbSerialDriver driver, Listener listener, byte EOL) {
        mDriver = driver;
        mListener = listener;
        mEolSymbol=EOL;
    }


    public synchronized void setListener(Listener listener) {
        mListener = listener;
    }

    public synchronized Listener getListener() {
        return mListener;
    }

  
    public synchronized void stop() {
        if (getState() == State.RUNNING) {
            Log.i(TAG, "Stop requested");
            mState = State.STOPPING;
        }
    }

    private synchronized State getState() {
        return mState;
    }

    /**
     * Continuously services the read and write buffers until {@link #stop()} is
     * called, or until a driver exception is raised.
     *
     * NOTE(mikey): Uses inefficient read/write-with-timeout.
     * TODO(mikey): Read asynchronously with {@link UsbRequest#queue(ByteBuffer, int)}
     */
    @Override
    public void run() {
        synchronized (this) {
            if (getState() != State.STOPPED) {
                throw new IllegalStateException("Already running.");
            }
            mState = State.RUNNING;
        }

        Log.i(TAG, "Running ..");
        try {
          mDataBuffer.clear();
            while (true) {
                
                if (getState() != State.RUNNING) {
                    Log.i(TAG, "Stopping mState=" + getState());
                    break;
                }
                stepReadln();
            }
        } catch (Exception e) {
            Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
            final Listener listener = getListener();
            if (listener != null) {
              listener.onRunError(e);
            }
        } finally {
            synchronized (this) {
                mState = State.STOPPED;
                Log.i(TAG, "Stopped.");
            }
        }
    }

    
    private void stepReadln() throws IOException {
        final Listener listener = getListener(); 
        if (listener != null) {
            mReadBuffer.clear();
            int len = ((CdcTusb3410SerialDriver) mDriver).readln(mReadBuffer.array(),getEolSymbol(), READ_WAIT_MILLIS);
            if (len > 0) {
                   if (DEBUG) Log.d(TAG, "Read data len=" + len);
                   final byte[] data = new byte[len];
                   mReadBuffer.get(data, 0, len);
                  listener.onNewData(data);
                                
                }

        }
       
    }
}
 
    


